package demo.db.mongo;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import demo.java.time.DateTimeDemo;
import demo.vo.TimeDemoVO;
import lombok.Getter;
import lombok.Setter;
import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexDefinition;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * MongoTemplate是线程安全的。
 * <p>
 * MongoTemplate实现了interface MongoOperations，一般推荐使用MongoOperations来进行相关的操作。
 * <p>
 * MongoDB documents和domain classes之间的映射关系是通过实现了MongoConverter这个interface的类来实现的。
 * <p>
 * 默认提供了两个SimpleMappingConverter（default） 和 MongoMappingConverter，但也可以自己定义。
 */
public class MongoTemplateDemo {

    private static Logger logger = LoggerFactory.getLogger(MongoTemplateDemo.class);

    private static final String CONNECTION_STRING = "mongodb://localhost:27017";
    private static final String DATABASE_NAME = "ops_alert";
    private static final String COLLECTION_NAME = "users";
    private MongoTemplate mongoTemplate;
    private MongoClient mongoClient;


    @Before
    public void buildMongoTemplate() {
        // 连接配置信息
        mongoClient = MongoClients.create(CONNECTION_STRING);
        mongoTemplate = new MongoTemplate(mongoClient, DATABASE_NAME);

    }

    @Test
    public void testInsert() {
        User user = new User();
        user.setName("Stan");
        user.setAge(25);
        user.setEmail("stan@example.com");
        user.setCity("上海");
        user.setHobbies(List.of("游戏", "运动", "编程"));
        User user1 = mongoTemplate.insert(user);// 插入单个文档
        System.err.println(user1);

        user1.setAge(32);
        // 保存（插入或更新）
        User user2 = mongoTemplate.save(user1);
        System.err.println(user2);

    }


    @Test
    public void testQuery() {
        // 查询所有
        List<User> allUsers = mongoTemplate.findAll(User.class);
        System.err.println(allUsers);

        // 根据 ID 查询
        User user = mongoTemplate.findById("123", User.class);
        System.err.println(user);

        // 条件查询
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is("Stan"));
        List<User> users = mongoTemplate.find(query, User.class);
        System.err.println(users);
        // 查询单个
        User user1 = mongoTemplate.findOne(query, User.class);
        System.err.println(user1);
        // 分页查询
        Query pageQuery = new Query()
                .with(Sort.by(Sort.Direction.DESC, "age"))
                .skip(10)
                .limit(5);
        List<User> userPage = mongoTemplate.find(pageQuery, User.class);
        System.err.println(userPage);

        // 多条件查询
        Criteria criteria = new Criteria();
        criteria.andOperator(
                Criteria.where("age").gte(18).lte(65),
                Criteria.where("name").regex("^J"),
                Criteria.where("status").is("ACTIVE")
        );
        query = new Query(criteria);

// 或条件
        Criteria orCriteria = new Criteria().orOperator(
                Criteria.where("status").is("ACTIVE"),
                Criteria.where("status").is("PENDING")
        );
    }

    @Test
    public void testUpdate() {
        // 更新操作
        Query query = new Query(Criteria.where("name").is("John"));
        Update update = new Update().set("age", 30).inc("version", 1);

        UpdateResult result = mongoTemplate.updateFirst(query, update, User.class);
        UpdateResult multiResult = mongoTemplate.updateMulti(query, update, User.class);

        // 查找并更新
        User updatedUser = mongoTemplate.findAndModify(
                query, update, FindAndModifyOptions.options().returnNew(true), User.class);
    }


    /**
     * 删除数据:
     */
    public void testDelete() {
        // 删除操作
        Query query = new Query(Criteria.where("name").is("John"));
        DeleteResult result = mongoTemplate.remove(query, User.class);

        // 删除所有
//        mongoTemplate.remove(new Query(), User.class);
    }


    /**
     * 索引
     */
    public void indexDemo() {
        IndexOperations indexOperations = mongoTemplate.indexOps("dkw_user_loan_info");
        // 创建索引
        IndexDefinition index = new Index("idcard", Direction.ASC);
        indexOperations.createIndex(index);
    }

    @Test
    public void distinct() {
        LocalDateTime now = LocalDateTime.now().minusDays(30);
        LocalDateTime d180Ago = now.minusDays(20).toLocalDate().atStartOfDay();
        // 查询条件
        ObjectId minInclude = new ObjectId(DateTimeDemo.from(d180Ago));
        ObjectId maxExclude = new ObjectId(DateTimeDemo.from(now));

        Query query = new Query(Criteria.where("_id").gt(minInclude).lt(maxExclude));
        System.out.println("====================");
    }

    @Test
    public void in() throws UnknownHostException {
        List<Long> list = Arrays.asList(88343880L, 17888885687L, 27888885689L);
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").in(list));
        List<JSONObject> lists = mongoTemplate.find(query, JSONObject.class, "user_call_log");
        System.out.println("====================");
        System.err.println(lists.size());

        DBObject query1 = new BasicDBObject(); // setup the query criteria 设置查询条件
        query1.put("_id", new BasicDBObject("$in", list));
        query1.put("time", (new BasicDBObject("$gte", 1)).append("$lte", 9));
    }

    /**
     * 查询索引信息
     *
     * @param collectionName
     */
    public void listIndex(String collectionName) {
        IndexOperations indexOperations = mongoTemplate.indexOps(collectionName);
        // 查询索引
        List<IndexInfo> indexInfos = indexOperations.getIndexInfo();
        if (indexInfos != null && !indexInfos.isEmpty()) {
            indexInfos.forEach(e -> {
                System.err.println(e.toString());
            });
        }
        System.err.println("================");
    }



    /**
     * <li>Criteria and (String key) Mongodb: $and 说明: 并且
     * <li>Criteria andOperator (Criteria…​ criteria) Mongodb: $and 说明:并且
     * <li>Criteria orOperator (Criteria…​ criteria) Mongodb: $or 说明:或者
     * <li>Criteria gt (Object o) Mongodb: $gt 说明:大于
     * <li>Criteria gte (Object o) Mongodb: $gte 说明:大于等于
     * <li>Criteria in (Object…​ o) Mongodb: $in 说明:包含
     * <li>Criteria is (Object o) Mongodb: $is 说明:等于
     * <li>Criteria lt (Object o) Mongodb: $lt 说明:小于
     * <li>Criteria lte (Object o) Mongodb: $lte 说明:小等于
     * <li>Criteria nin (Object…​ o) Mongodb: $nin 说明:不包含
     *
     */
    void demoCriteria() {

    }

    /**
     * <h1>spring-mongodb-data 返回指定字段</h1>指定返回字段，有时文档字段多并数据大时，我们指定返回我们需要的字段，这样既节省传输数据量，减少了内存消耗，提高了性能，在数据大时，性能很明显的。
     * <p>
     * <li>db.collection.find(query, projection)
     * <li>query ：可选，使用查询操作符指定查询条件
     * <li>projection ：可选，使用投影操作符指定返回的键。查询时返回文档中所有键值， 只需省略该参数即可（默认省略）。 BasicQuery查询语句可以指定返回字段，
     *
     *
     * <li>构造函数: BasicQuery(DBObject queryObject, DBObject fieldsObject)
     * <li>fieldsObject 这个字段可以指定返回字段
     * <li>fieldsObject.put(key,value)
     * <li>key：字段
     * <li>value：1或者true表示返回字段;0或者false表示不返回该字段
     * <li>_id:默认就是1，没指定返回该字段时，默认会返回，除非设置为0是，就不会返回该字段。
     */
    void testFields() {
        // 查询条件
        DBObject queryObject = new BasicDBObject();
        queryObject.put("name", "zhangsan");

        BasicDBObject fieldsObject = new BasicDBObject();
        // 指定返回的字段
        fieldsObject.put("name", true);
        fieldsObject.put("age", true);
        fieldsObject.put("sex", true);
    }

    /**
     * 模糊查询
     *
     * @param conditions
     * @return
     */
    public long getProcessLandLogsCount(Map<String, String> conditions) {
        Query query = new Query();
        if (conditions != null && conditions.size() > 0) {
            for (Map.Entry<String, String> condition : conditions.entrySet()) {
                query.addCriteria(
                        Criteria.where(condition.getKey()).regex(".*?\\" + condition.getValue().toString() + ".*"));
            }
        }
        return mongoTemplate.count(query, Object.class);
    }


    /**
     * 插入一条数据
     *
     * @throws UnknownHostException
     * @throws InterruptedException
     */
    @Test
    public void save() throws UnknownHostException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(8);

        for (int id = 0; id < 80; id++) {
            int id1 = id;
            Date now = new Date();
            for (int i = 0; i < 8; i++) {
                int j = i;
                executorService.submit(() -> {
                    TimeDemoVO obj = mongoTemplate.findById(id1, TimeDemoVO.class);
                    if (obj == null) {
                        obj = new TimeDemoVO();
                        obj.setId(id1);
                    }
                    obj.setDate(now);
                    mongoTemplate.save(obj);
                    logger.info("save {}", j);
                });
            }
        }

        executorService.awaitTermination(5, TimeUnit.SECONDS);
    }

    @Getter
    @Setter
    @Document(collection = COLLECTION_NAME)
    public static class User {
        @Id
        private String id;
        private String name;
        private int age;
        private String email;
        private String city;
        private List<String> hobbies;

        @Override
        public String toString() {
            return JSON.toJSONString(this);
        }
    }
}
